import {
  BloomCompositeMaterial,
  SeperableBlurUnrealFragment,
  SeperableBlurVertex,
} from '../shader/shader_outline_helper.js';
import * as THREE from "../../../../modules/three/three.module.js";
import {LuminosityHighPassShader} from "../../../../modules/three/jsm/shaders/LuminosityHighPassShader.js";

const darkMaterial = new THREE.MeshBasicMaterial({ color: 'black' });
const oldClearColor = new THREE.Color();
const BlurDirectionX = new THREE.Vector2(1.0, 0.0);
const BlurDirectionY = new THREE.Vector2(0.0, 1.0);

export default class UnrealBloomHelper {
  /**
   * 选中对象
   * @type {THREE.Object3D[]}
   */
  selectedObjects = [];
  /**
   * 选中对象的所有网格
   * @type {THREE.Mesh[]}
   * @private
   */
  _selectedMeshes = [];
  _hiddenObjs = [];
  _params = { strength: 1, radius: 0.03, threshold: 0.0 };

  _bakMaterials = new WeakMap();

  /**
   * 用于显示3d模型的轮廓
   * @param {THREE.WebGLRenderer} renderer
   * @param {THREE.Scene} renderScene
   * @param {THREE.Camera} renderCamera
   */
  constructor(renderer, renderScene, renderCamera) {
    this.renderer = renderer;
    this.renderScene = renderScene;
    this.renderCamera = renderCamera;

    const scene = new THREE.Scene();
    scene.matrixWorldAutoUpdate = false;
    const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
    const _geometry = new THREE.BufferGeometry();
    _geometry.setAttribute(
      'position',
      new THREE.Float32BufferAttribute([-1, 3, 0, -1, -1, 0, 3, -1, 0], 3),
    );
    _geometry.setAttribute(
      'uv',
      new THREE.Float32BufferAttribute([0, 2, 0, 0, 2, 0], 2),
    );
    const quad = new THREE.Mesh(_geometry, null);
    this._quad = quad;
    scene.add(quad);
    this._scene = scene;
    this._camera = camera;
    this.onCanvasResize();
  }

  setParams(obj) {
    if (obj.hasOwnProperty('strength')) {
      this._params.strength = obj.strength;
      this.compositeMaterial.uniforms.bloomStrength.value = obj.strength;
    }
    if (obj.hasOwnProperty('radius')) {
      this._params.radius = obj.radius;
      this.compositeMaterial.uniforms.bloomRadius.value = obj.radius;
    }
    if (obj.hasOwnProperty('threshold')) {
      this._params.threshold = obj.threshold;
      this.materialHighPassFilter.uniforms.luminosityThreshold.value = obj.threshold;
    }
  }

  getParams() {
    return this._params;
  }

  updateStrength(strength) {
    this.compositeMaterial.uniforms.bloomStrength.value = strength;
  }

  onCanvasResize() {
    const size = new THREE.Vector2();
    this.renderer.getDrawingBufferSize(size);
    const pixelRatio = this.renderer.getPixelRatio();
    size.multiplyScalar(pixelRatio);
    const { strength, radius, threshold } = this._params;
    this.resolution = size;

    // create color only once here, reuse it later inside the render function
    this.clearColor = new THREE.Color('#000');

    // render targets
    this.renderTargetsHorizontal = [];
    this.renderTargetsVertical = [];
    this.nMips = 5;
    let resx = Math.round(this.resolution.x / 2);
    let resy = Math.round(this.resolution.y / 2);

    this._baseBuffer = new THREE.WebGLRenderTarget(resx, resy);
    this._baseBuffer.texture.generateMipmaps = false;

    this.renderTargetBright = new THREE.WebGLRenderTarget(resx, resy);
    this.renderTargetBright.texture.generateMipmaps = false;

    for (let i = 0; i < this.nMips; i++) {
      const renderTargetHorizonal = new THREE.WebGLRenderTarget(resx, resy);
      renderTargetHorizonal.texture.name = 'UnrealBloomPass.h' + i;
      renderTargetHorizonal.texture.generateMipmaps = false;
      this.renderTargetsHorizontal.push(renderTargetHorizonal);
      const renderTargetVertical = new THREE.WebGLRenderTarget(resx, resy);
      renderTargetVertical.texture.name = 'UnrealBloomPass.v' + i;
      renderTargetVertical.texture.generateMipmaps = false;
      this.renderTargetsVertical.push(renderTargetVertical);
      resx = Math.round(resx / 2);
      resy = Math.round(resy / 2);
    }

    const highPassShader = LuminosityHighPassShader;
    const highPassUniforms = THREE.UniformsUtils.clone(highPassShader.uniforms);
    highPassUniforms.luminosityThreshold.value = threshold;
    highPassUniforms.smoothWidth.value = 0.01;
    highPassUniforms.tDiffuse.value = this._baseBuffer.texture;
    this.materialHighPassFilter = new THREE.ShaderMaterial({
      uniforms: highPassUniforms,
      vertexShader: highPassShader.vertexShader,
      fragmentShader: highPassShader.fragmentShader,
      defines: {},
    });

    // Gaussian Blur Materials
    this.separableBlurMaterials = [];
    const kernelSizeArray = [3, 5, 7, 9, 11];
    resx = Math.round(this.resolution.x / 2);
    resy = Math.round(this.resolution.y / 2);
    for (let i = 0; i < this.nMips; i++) {
      this.separableBlurMaterials.push(
        this._getSeperableBlurMaterial(kernelSizeArray[i]),
      );
      this.separableBlurMaterials[i].uniforms.texSize.value = new THREE.Vector2(
        resx,
        resy,
      );
      resx = Math.round(resx / 2);
      resy = Math.round(resy / 2);
    }

    // Composite material
    const white = new THREE.Vector3(1, 1, 1);
    this.compositeMaterial = new THREE.ShaderMaterial({
      defines: {
        NUM_MIPS: this.nMips,
      },

      uniforms: {
        blurTexture1: { value: this.renderTargetsVertical[0].texture },
        blurTexture2: { value: this.renderTargetsVertical[1].texture },
        blurTexture3: { value: this.renderTargetsVertical[2].texture },
        blurTexture4: { value: this.renderTargetsVertical[3].texture },
        blurTexture5: { value: this.renderTargetsVertical[4].texture },
        dirtTexture: { value: null },
        bloomStrength: { value: strength },
        bloomFactors: { value: [1.0, 0.8, 0.6, 0.4, 0.2] },
        bloomTintColors: { value: [white, white, white, white, white] },
        bloomRadius: { value: radius },
      },
      vertexShader: SeperableBlurVertex,
      fragmentShader: BloomCompositeMaterial,
      blending: THREE.NormalBlending,
      transparent: true,
      depthTest: false,
      toneMapped: false,
      side: THREE.DoubleSide,
    });
  }

  _getSeperableBlurMaterial(kernelRadius) {
    return new THREE.ShaderMaterial({
      vertexShader: SeperableBlurVertex,
      fragmentShader: SeperableBlurUnrealFragment,
      defines: {
        KERNEL_RADIUS: kernelRadius,
        SIGMA: kernelRadius,
      },
      uniforms: {
        colorTexture: {
          value: null,
        },
        texSize: {
          value: new THREE.Vector2(0.5, 0.5),
        },
        direction: {
          value: new THREE.Vector2(0.5, 0.5),
        },
      },
      depthTest: false,
      toneMapped: false,
      side: THREE.DoubleSide,
    });
  }

  _darkenNonSelectedObjects() {
    this.renderScene.traverse((obj3d) => {
      // if (!obj3d.isMesh) {
      //   if (obj3d.geometry) {
      //     this._hiddenObjs.push(obj3d)
      //     obj3d.userData.oldVisible = obj3d.visible
      //     obj3d.visible = false
      //   }
      //   return
      // }
      if (!this._selectedMeshes.includes(obj3d) && obj3d.visible) {
        obj3d.material = darkMaterial;
      }
    });
  }

  _undarkenObjects() {
    this.renderScene.traverse((obj3d) => {
      const material = this._bakMaterials.get(obj3d);
      if (material) {
        obj3d.material = material;
      }
    });
    this._hiddenObjs.forEach((obj) => (obj.visible = obj.userData.oldVisible));
    this._hiddenObjs = [];
  }

  setRenderCamera(camera) {
    this.renderCamera = camera;
  }

  selectSingle(obj) {
    this.selectedObjects = [obj];
    this._onSelectionChanged();
  }

  setHoverSingle() {}
  unHover() {}

  select(obj) {
    if (obj && !this.selectedObjects.includes(obj)) {
      this.selectedObjects.push(obj);
      this._onSelectionChanged();
    }
  }

  unSelect(obj) {
    if (obj) {
      const index = this.selectedObjects.indexOf(obj);
      if (index >= 0) {
        this.selectedObjects.splice(index, 1);
        this._onSelectionChanged();
      }
    } else {
      this.selectedObjects = [];
      this._onSelectionChanged();
    }
  }

  _onSelectionChanged() {
    this._selectedMeshes = [];
    this.selectedObjects.forEach((obj) => {
      obj.traverse((obj3d) => {
        // if (obj3d.isMesh) {
        this._selectedMeshes.push(obj3d);
        // }
      });
    });
    this._undarkenObjects();
    this._bakMaterials = new WeakMap();
    this.renderScene.traverse((obj3d) => {
      // if (!obj3d.isMesh) {
      //   return
      // }
      if (!this._selectedMeshes.includes(obj3d)) {
        this._bakMaterials.set(obj3d, obj3d.material);
      }
    });
  }

  /**
   * @param {Scene3d} scene3d
   */
  onAfterRender(scene3d) {
    if (this.selectedObjects.length === 0) {
      return;
    }

    const renderer = this.renderer;
    // 记录原始状态
    const oldOverrideMaterial = this.renderScene.overrideMaterial;
    const oldBackground = this.renderScene.background;

    const oldAutoClear = renderer.autoClear;
    renderer.getClearColor(oldClearColor);
    const oldClearAlpha = renderer.getClearAlpha();
    const oldRenderTarget = renderer.getRenderTarget();

    renderer.autoClear = false;
    renderer.setClearColor(0x0, 1);
    this._darkenNonSelectedObjects();
    // 1. Extract Bright Areas
    renderer.setRenderTarget(this._baseBuffer);
    renderer.clear();
    scene3d.store.simpleMarks.forEach(it => it.removeFromParent());
    scene3d.store.lightSources.forEach(it => it.light.removeFromParent());
    renderer.render(this.renderScene, this.renderCamera);

    this._quad.material = this.materialHighPassFilter;
    renderer.setRenderTarget(this.renderTargetBright);
    renderer.clear();
    renderer.render(this._scene, this._camera);
    // 2. Blur All the mips progressively
    let inputRenderTarget = this.renderTargetBright;
    for (let i = 0; i < this.nMips; i++) {
      this._quad.material = this.separableBlurMaterials[i];
      this.separableBlurMaterials[i].uniforms.colorTexture.value =
        inputRenderTarget.texture;
      this.separableBlurMaterials[i].uniforms.direction.value = BlurDirectionX;
      renderer.setRenderTarget(this.renderTargetsHorizontal[i]);
      renderer.clear();
      renderer.render(this._scene, this._camera);
      this.separableBlurMaterials[i].uniforms.colorTexture.value =
        this.renderTargetsHorizontal[i].texture;
      this.separableBlurMaterials[i].uniforms.direction.value = BlurDirectionY;
      renderer.setRenderTarget(this.renderTargetsVertical[i]);
      renderer.clear();
      renderer.render(this._scene, this._camera);
      inputRenderTarget = this.renderTargetsVertical[i];
    }
    // Composite All the mips
    // 与原场景叠加
    renderer.setClearColor(0xffffff, 1);
    this._quad.material = this.compositeMaterial;
    renderer.setRenderTarget(null);
    // renderer.clear()
    renderer.render(this._scene, this._camera);

    // 还原原始状态
    this._undarkenObjects();
    this.renderScene.overrideMaterial = oldOverrideMaterial;
    this.renderScene.background = oldBackground;

    renderer.autoClear = oldAutoClear;
    renderer.setClearColor(oldClearColor);
    renderer.setClearAlpha(oldClearAlpha);
    renderer.setRenderTarget(oldRenderTarget);
    scene3d.store.simpleMarks.forEach(it => scene3d.scene.add(it));
    scene3d.store.lightSources.forEach(it => it.attachToScene(scene3d.scene));
  }
}
